﻿// $URL$
// $Id$

// Copyright © Jason Curl 2012-2015
// See http://serialportstream.codeplex.com for license details (MS-PL License)

// Enable the following define to enable behaviour in the number of bytes similar to the MS
// implementation. It's recommended not to do this, as it's not consistent with the rest of
// this implementation.
//#define DRIVERBUFFEREDBYTES

namespace RJCP.IO.Ports
{
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.IO;
    using System.Management;
    using System.Threading;
    using System.Runtime.Remoting.Messaging;
    using Microsoft.Win32;
    using Datastructures;
    using Native;

    /// <summary>
    /// The SerialPortStream is a stream class to communicate with serial port based devices.
    /// </summary>
    /// <remarks>
    /// This implementation is a ground up reimplementation of the Microsoft SerialPort class
    /// but one that is a stream. There are numerous small issues with the Microsoft .NET 4.0
    /// implementation (and assumed earlier) that this class attempts to resolve.
    /// <para>For detailed information about serial port programming, refer to the site:
    /// http://msdn.microsoft.com/en-us/library/ms810467.aspx</para>
    /// <para>When instantiating.</para>
    /// </remarks>
    public partial class SerialPortStream : Stream
    {
        private NativeSerialPort m_SerialPort;

        private byte m_ParityReplace;
        private bool m_BreakState;

        #region Private Variables - ReadTo() implementation
        /// <summary>
        /// This is the maximum amount of buffer space to allocate when using the ReadTo().
        /// method.
        /// </summary>
        private const int c_MaxLine = 1024;

        /// <summary>
        /// Converted characters from bytes generated by the ReadTo() method.
        /// </summary>
        private CircularBuffer<char> m_Read = new CircularBuffer<char>(c_MaxLine);

        /// <summary>
        /// Offsets into the byte buffer for each character in m_Read.
        /// </summary>
        private CircularBuffer<int> m_ReadOffsets = new CircularBuffer<int>(c_MaxLine);

        /// <summary>
        /// If the last byte has consumed byte data but not yet generated a character.
        /// </summary>
        private bool m_ReadIncomplete;

        /// <summary>
        /// Cached offset in the byte buffer which corresponds to the next character.
        /// </summary>
        private int m_ReadOffset;

        /// <summary>
        /// If ReadTo is reading more data than we have in m_Read.
        /// </summary>
        /// <remarks>
        /// If this is not -1, it implies that other functions ReadChar(), Read(char[], ...) shouldn't
        /// obtain bytes from here.
        /// </remarks>
        private int m_ReadOverflow = -1;

        /// <summary>
        /// First character in the byte buffer in case of a read overflow.
        /// </summary>
        private char m_ReadOverflowChar;

        /// <summary>
        /// Last string sought for.
        /// </summary>
        private string m_ReadToString;
        #endregion

        #region Public constants
        /// <summary>
        /// Indicates that no timeout should occur.
        /// </summary>
        public const int InfiniteTimeout = -1;
        #endregion

        #region Constructor, Port Name, Open
        /// <summary>
        /// Constructor. Create a stream that doesn't connect to any port.
        /// </summary>
        /// <remarks>
        /// This constructor initialises a stream object, but doesn't assign it to any COM port.
        /// The properties then assume default settings. No COM port is opened and queried.
        /// </remarks>
        public SerialPortStream() : this(null) { }

        /// <summary>
        /// Constructor. Create a stream that connects to the specified port.
        /// </summary>
        /// <remarks>
        /// This constructor attempts to bind directly to the port given. Properties assume
        /// the settings of the port provided. Exceptions may occur if the port cannot be
        /// opened.
        /// </remarks>
        /// <param name="port">The name of the COM port, such as "COM1" or "COM33".</param>
        public SerialPortStream(string port)
        {
            GetPortSettings(null);
            if (port != null) PortName = port;
        }

        /// <summary>
        /// Constructor. Create a stream that connects to the specified port and sets the initial baud rate.
        /// </summary>
        /// <remarks>
        /// The stream doesn't impose any arbitrary limits on setting the baud rate. It is passed
        /// directly to the driver and it is up to the driver to determine if the baud rate is
        /// settable or not. Normally, a driver will attempt to set a baud rate that is within 5%
        /// of the requested baud rate (but not guaranteed). 
        /// </remarks>
        /// <param name="port">The name of the COM port, such as "COM1" or "COM33".</param>
        /// <param name="baud">The baud rate that is passed to the underlying driver.</param>
        public SerialPortStream(string port, int baud)
            : this(port)
        {
            BaudRate = baud;
        }

        /// <summary>
        /// Constructor. Create a stream that connects to the specified port with standard parameters.
        /// </summary>
        /// <remarks>
        /// The stream doesn't impose any arbitrary limits on setting the baud rate. It is passed
        /// directly to the driver and it is up to the driver to determine if the baud rate is
        /// settable or not. Normally, a driver will attempt to set a baud rate that is within 5%
        /// of the requested baud rate (but not guaranteed). 
        /// <para>Not all combinations are supported. The driver will interpret the data and indicate
        /// if configuration is possible or not.</para>
        /// </remarks>
        /// <param name="port">The name of the COM port, such as "COM1" or "COM33".</param>
        /// <param name="baud">The baud rate that is passed to the underlying driver.</param>
        /// <param name="data">The number of data bits. This is checked that the driver 
        /// supports the data bits provided. The special type 16X is not supported.</param>
        /// <param name="parity">The parity for the data stream.</param>
        /// <param name="stopbits">Number of stop bits.</param>
        public SerialPortStream(string port, int baud, int data, Parity parity, StopBits stopbits)
            : this(port)
        {
            BaudRate = baud;
            DataBits = data;
            Parity = parity;
            StopBits = stopbits;
        }

        /// <summary>
        /// Gets the port for communications, including but not limited to all available COM ports.
        /// </summary>
        /// <remarks>
        /// A list of valid port names can be obtained using the GetPortNames method.
        /// <para>When changing the port name, and the property UpdateOnPortSet is <b>true</b>, setting
        /// this property will cause the port to be opened, status read and the port then closed. Thus, you
        /// can use this behaviour to determine the actual settings of the port (which remain constant
        /// until a program actually changes the port settings).</para>
        /// <para>Setting this property to itself, while having UpdateOnPortSet to <b>true</b> has the
        /// effect of updating the local properties based on the current port settings.</para>
        /// </remarks>
        public string PortName
        {
            get { return m_SerialPort.Port; }
            set
            {
                if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
                if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException("Must provide a valid name for a COM port");
                if (IsOpen && value != m_SerialPort.Port) throw new InvalidOperationException("Serial Port already opened");

                m_SerialPort.Port = value;
            }
        }

        /// <summary>
        /// Update properties based on the current port, overwriting already existing properties.
        /// </summary>
        /// <remarks>
        /// This method opens the serial port and retrieves the current settings from Windows.
        /// These settings are then made available via the various properties, BaudRate, DataBits,
        /// Parity, ParityReplace, Handshake, StopBits, TxContinueOnXoff, DiscardNull, XOnLimit 
        /// and XOffLimit.
        /// </remarks>
        public void GetPortSettings()
        {
            if (IsOpen) throw new InvalidOperationException("Serial Port already opened");
            GetPortSettings(m_SerialPort.Port);
        }

        /// <summary>
        /// Get the properties of the specified serial port.
        /// </summary>
        /// <remarks>
        /// If no instance to a NativeSerialPort is created, we instantiate a new one
        /// and get the properties. The port is closed afterwards.
        /// <para>If an instance to a NativeSerialPort exists, then the properties of the
        /// serial port are reread. If the port is open, it remains open, else the port
        /// is temporarily opened and then closed.</para>
        /// <para>Note, the windows feature RTS_CONTROL_TOGGLE is not supported by this class.</para>
        /// </remarks>
        /// <param name="port">The port to open.</param>
        private void GetPortSettings(string port)
        {
            bool setPort = false;

            if (m_SerialPort == null) {
                // We need to create a new instance. This is called from the constructor.
                if (port == null) {
                    m_SerialPort = new NativeSerialPort();

                    // Set the default properties of the DCB. We must do this here, so that
                    // this object has decent defaults, as we don't query the OS at all on
                    // what normal values should be.
                    BaudRate = 115200;
                    DataBits = 8;
                    Parity = Parity.None;
                    StopBits = StopBits.One;
                    Handshake = Handshake.None;
                    TxContinueOnXOff = false;
                    DiscardNull = false;
                    XOnLimit = 2048;
                    XOffLimit = 512;
                    ParityReplace = 0;
                } else {
                    try {
                        m_SerialPort = new NativeSerialPort(port);
                        m_SerialPort.SerialPortCommState.GetCommState();
                        m_SerialPort.SerialPortCommProperties.GetCommProperties();
                        m_SerialPort.SerialPortModemStatus.GetCommModemStatus();
                        m_SerialPort.Close();
                    } catch {
                        m_SerialPort = new NativeSerialPort();
                        m_SerialPort.Port = port;

                        BaudRate = 115200;
                        DataBits = 8;
                        Parity = Parity.None;
                        StopBits = StopBits.One;
                    }
                }

                m_BreakState = false;

                m_SerialPort.SerialPortIo.CommEvent += SerialPortIo_CommEvent;
                m_SerialPort.SerialPortIo.CommErrorEvent += SerialPortIo_CommErrorEvent;
            } else {
                if (m_SerialPort.IsOpen) {
                    if (port == null || m_SerialPort.Port == port) {
                        // Port is already open, no change in port specified, so just get the state
                        m_SerialPort.SerialPortCommState.GetCommState();
                        m_SerialPort.SerialPortCommProperties.GetCommProperties();
                        m_SerialPort.SerialPortModemStatus.GetCommModemStatus();
                        // The break state isn't affected here
                    } else {
                        // Port is already open, but port should change. Strange situation, probably
                        // not what is wanted, so ensure in the rest of the class this case is avoided.
                        m_SerialPort.Close();
                        m_SerialPort.Open(port);
                        m_SerialPort.SerialPortCommState.GetCommState();
                        m_SerialPort.SerialPortCommProperties.GetCommProperties();
                        m_SerialPort.SerialPortModemStatus.GetCommModemStatus();
                        if (m_BreakState) {
                            m_SerialPort.SerialPortModemStatus.SetCommBreak();
                        } else {
                            m_SerialPort.SerialPortModemStatus.ClearCommBreak();
                        }
                    }
                } else {
                    if (port == null || m_SerialPort.Port == port) {
                        // Port is not open, reopen it, get the state and close it again
                        m_SerialPort.Open();
                    } else {
                        // Port is not open, user wants to change the port. Get the state, close it
                        m_SerialPort.Open(port);
                    }
                    m_SerialPort.SerialPortCommState.GetCommState();
                    m_SerialPort.SerialPortCommProperties.GetCommProperties();
                    m_SerialPort.SerialPortModemStatus.GetCommModemStatus();
                    m_SerialPort.SerialPortModemStatus.ClearCommBreak();
                    m_BreakState = false;
                    m_SerialPort.Close();
                }
            }

            // Binary mode must always be set per MSDN
            m_SerialPort.SerialPortCommState.Binary = true;

            // Ensure the parity settings are consistent
            if (!m_SerialPort.SerialPortCommState.ParityEnable) {
                if (m_SerialPort.SerialPortCommState.Parity != Parity.None) {
                    m_SerialPort.SerialPortCommState.Parity = Parity.None;
                    setPort = true;
                }
            }

            // Get the Error Char. Only if it is zero and parity is enabled, we change it to 126
            // This is because the interface can't provide for an error char of zero and active.
            // This is a limitation of the API as taken over from the System.IO.Ports.SerialPort
            // implementation by Microsoft.
            m_ParityReplace = m_SerialPort.SerialPortCommState.ErrorChar;
            if (m_ParityReplace == 0 && m_SerialPort.SerialPortCommState.ErrorCharEnabled &&
                m_SerialPort.SerialPortCommState.ParityEnable && m_SerialPort.SerialPortCommState.Parity != Parity.None) {
                m_ParityReplace = 126;
            }

            // The function fAbortOnError should be disabled
            if (m_SerialPort.SerialPortCommState.AbortOnError) {
                m_SerialPort.SerialPortCommState.AbortOnError = false;
                setPort = true;
            }

            // Event and EOF chars, compatible with MS implementation
            if (m_SerialPort.SerialPortCommState.EventChar != 26) {
                m_SerialPort.SerialPortCommState.EventChar = 26;
                setPort = true;
            }
            if (m_SerialPort.SerialPortCommState.EofChar != 26) {
                m_SerialPort.SerialPortCommState.EofChar = 26;
                setPort = true;
            }

            // We don't support RTS_CONTROL_TOGGLE
            if (m_SerialPort.SerialPortCommState.RtsControl == NativeSerialPort.RtsControl.Toggle) {
                m_SerialPort.SerialPortCommState.RtsControl = NativeSerialPort.RtsControl.Disable;
                setPort = true;
            }

            // Set the XOn and XOff characters
            if (m_SerialPort.SerialPortCommState.XOnChar != 17) {
                m_SerialPort.SerialPortCommState.XOnChar = 17;
                setPort = true;
            }
            if (m_SerialPort.SerialPortCommState.XOffChar != 19) {
                m_SerialPort.SerialPortCommState.XOffChar = 19;
                setPort = true;
            }

            if (m_SerialPort.IsOpen && setPort) m_SerialPort.SerialPortCommState.SetCommState();
        }

        /// <summary>
        /// Opens a new serial port connection.
        /// </summary>
        /// <exception cref="InvalidOperationException">This object is already managing a serial port
        /// connection.</exception>
        /// <exception cref="System.ObjectDisposedException">SerialPortStream is disposed of.</exception>
        /// <remarks>
        /// Opens a connection to the serial port provided by the constructor or the Port property.
        /// If this object is already managing a serial port, this object raises an exception.
        /// <para>When opening the port, only the settings explicitly applied will be given to the
        /// port. That is, if you read the default BaudRate as 115200, this value will only be
        /// applied if you explicitly set it to 115200. Else the default baud rate of the serial
        /// port when its opened will be used.</para>
        /// <para>Normally when you instantiate this stream on a COM port, it is opened for a brief
        /// time and queried for the capabilities and default settings. This allows your application
        /// to use the settings that were already available (such as defined by the windows user
        /// in the Control Panel, or the last open application). If you require to open the COM
        /// port without briefly opening it to query its status, then you need to instantiate
        /// this object through the default constructor. Set the property UpdateOnPortSet to false
        /// and then set the Port property. Provide all the other properties you require then call
        /// the Open() method. The port will be opened using the default properties providing
        /// you with a consistent environment (independent of the state of the Operating System
        /// or the driver beforehand).</para>
        /// </remarks>
        public void Open()
        {
            Open(true);
        }

        /// <summary>
        /// Opens a new serial port connection with control if the port settings are initialised or not.
        /// </summary>
        /// <exception cref="System.ObjectDisposedException">SerialPortStream is disposed of.</exception>
        /// <exception cref="System.InvalidOperationException">Serial Port already opened</exception>
        /// <remarks>
        /// Opens a connection to the serial port provided by the constructor or the Port property.
        /// If this object is already managing a serial port, this object raises an exception.
        /// <para>You can override the open so that no communication settings are retrieved or set.
        /// This is useful for virtual COM ports that do not manage state bits (some as some emulated
        /// COM ports or USB based communications that present themselves as a COM port but do not have
        /// any underlying physical RS232 implementation).</para>
        /// <note type="note">If you use this method to avoid setting parameters for the serial port,
        /// instead only to open the serial port, you should be careful not to set any properties
        /// associated with the serial port, as they will set the communications properties.</note>
        /// </remarks>
        public void OpenDirect()
        {
            Open(false);
        }

        private void Open(bool setCommState)
        {
            if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
            if (m_SerialPort.IsOpen) throw new InvalidOperationException("Serial Port already opened");

            m_SerialPort.Open();
            if (setCommState) {
                m_SerialPort.SerialPortCommState.SetCommState();

                // Fetch the actual settings and get the capabilities
                GetPortSettings(null);

                try {
                    m_SerialPort.SerialPortModemStatus.ClearCommBreak();
                } catch (System.IO.IOException) {
                    // Ignore IOException. Not all serial port drivers support clearing the
                    // Break signal, so we ignore it when opening.
                }

                // Set the state of the RTS line if handshaking is disabled
                if (!m_SerialPort.SerialPortCommState.OutCtsFlow) {
                    // OutCtsFlow is only enabled if RtsControl is handshaking.
                    m_SerialPort.SerialPortModemStatus.SetRts(RtsEnable);
                }

                // Set the state of the DTR line if handshaking is disabled
                if (!m_SerialPort.SerialPortCommState.OutDsrFlow) {
                    // OutDsrFlow is only enabled if DtrControl is handshaking
                    m_SerialPort.SerialPortModemStatus.SetDtr(DtrEnable);
                }
            }

            // Create threads and start working with local buffers
            m_SerialPort.SerialPortIo.Start();
        }

        /// <summary>
        /// Gets a value indicating the open or closed status of the SerialPortStream object.
        /// </summary>
        /// <remarks>
        /// The IsOpen property tracks whether the port is open for use by the caller, not
        /// whether the port is open by any application on the machine.
        /// </remarks>
        /// <value>
        /// True if the serial port is open; otherwise, false. The default is false.
        /// </value>
        public bool IsOpen
        {
            get { return m_SerialPort.IsOpen; }
        }
        #endregion

        #region Events
        /// <summary>
        /// Event raised when data is received, or the EOF character is detected by the driver.
        /// </summary>
        public event SerialDataReceivedEventHandler DataReceived;

        /// <summary>
        /// Event raised when an error condition is detected.
        /// </summary>
        public event SerialErrorReceivedEventHandler ErrorReceived;

        /// <summary>
        /// Event raised when Modem PIN changes are detected.
        /// </summary>
        public event SerialPinChangedEventHandler PinChanged;
        #endregion

        #region Computer Configuration and Ports
        /// <summary>
        /// Gets an array of serial port names for the current computer.
        /// </summary>
        /// <returns>An array of serial port names for the current computer.</returns>
        public static string[] GetPortNames()
        {
            using (RegistryKey local = Registry.LocalMachine.OpenSubKey(@"HARDWARE\DEVICEMAP\SERIALCOMM", false)) {
                if (local == null) return new string[0];
                string[] k = local.GetValueNames();
                if (k.Length > 0) {
                    string[] ports = new string[local.ValueCount];
                    for (int i = 0; i < k.Length; i++) {
                        ports[i] = local.GetValue(k[i]) as string;
                    }
                    return ports;
                }
                return new string[0];
            }
        }

        /// <summary>
        /// Gets an array of serial port names and descriptions for the current computer.
        /// </summary>
        /// <remarks>
        /// This method uses the Windows Management Interface to obtain its information. Therefore,
        /// the list may be different to the list obtained using the GetPortNames() method which
        /// uses other techniques.
        /// <para>On Windows 7, this method shows to return normal COM ports, but not those
        /// associated with a modem driver.</para>
        /// </remarks>
        /// <returns>An array of serial ports for the current computer.</returns>
        public static PortDescription[] GetPortDescriptions()
        {
            Dictionary<string, PortDescription> list = new Dictionary<string, PortDescription>();
            using (RegistryKey local = Registry.LocalMachine.OpenSubKey(@"HARDWARE\DEVICEMAP\SERIALCOMM", false)) {
                if (local != null) {
                    string[] k = local.GetValueNames();
                    foreach (string p in k) {
                        string n = local.GetValue(p) as string;
                        list.Add(n, new PortDescription(n, ""));
                    }
                }
            }

            ManagementObjectCollection objects;
            // Look for standard serial ports
            using (ManagementObjectSearcher q = new ManagementObjectSearcher("select * from Win32_SerialPort")) {
                objects = q.Get();
                using (ManagementObjectCollection.ManagementObjectEnumerator enumerator = objects.GetEnumerator()) {
                    while (enumerator.MoveNext()) {
                        ManagementObject current = (ManagementObject)enumerator.Current;
                        string k = current["DeviceID"].ToString();
                        if (!list.ContainsKey(k)) {
                            list.Add(k, new PortDescription(k, current["Name"].ToString()));
                        }
                    }
                }
            }

            // Look for any modems that are attached to COM ports that aren't listed above
            using (ManagementObjectSearcher q = new ManagementObjectSearcher("select * from Win32_POTSModem")) {
                objects = q.Get();
                using (ManagementObjectCollection.ManagementObjectEnumerator enumerator = objects.GetEnumerator()) {
                    while (enumerator.MoveNext()) {
                        ManagementObject current = (ManagementObject)enumerator.Current;
                        string k = current["AttachedTo"].ToString();
                        if (!list.ContainsKey(k)) {
                            list.Add(k, new PortDescription(k, current["Name"].ToString()));
                        }
                    }
                }
            }

            // Get the array and return it
            int i = 0;
            PortDescription[] ports = new PortDescription[list.Count];
            foreach (PortDescription p in list.Values) {
                ports[i++] = p;
            }
            return ports;
        }
        #endregion

        #region Reading and Writing Configuration
        private Encoding m_Encoding = Encoding.GetEncoding("UTF-8");
        private Decoder m_Decoder;

        /// <summary>
        /// Gets or sets the byte encoding for pre- and post-transmission conversion of text.
        /// </summary>
        /// <remarks>
        /// The encoding is used for encoding string information to byte format when sending
        /// over the serial port, or receiving data via the serial port. It is only used
        /// with the read/write functions that accept strings (and not used for byte based
        /// reading and writing).
        /// </remarks>
        public Encoding Encoding
        {
            get { return m_Encoding; }
            set
            {
                if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
                if (value != null) {
                    m_Encoding = value;
                    m_Decoder = null;
                } else {
                    throw new ArgumentNullException("value", "Encoding may not be null");
                }
            }
        }

        /// <summary>
        /// Get the Decoder associated with the current Encoding class.
        /// </summary>
        private Decoder Decoder
        {
            get
            {
                if (m_Encoding != null) {
                    if (m_Decoder == null) m_Decoder = m_Encoding.GetDecoder();
                    return m_Decoder;
                } else {
                    return null;
                }
            }
        }

        private string m_NewLine = "\n";

        /// <summary>
        /// Gets or sets the value used to interpret the end of a call to the
        /// ReadLine and WriteLine methods.
        /// </summary>
        /// <remarks>
        /// A value that represents the end of a line. The default is a line feed, (NewLine).
        /// </remarks>
        public string NewLine
        {
            get { return m_NewLine; }
            set
            {
                if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
                if (value == null) throw new ArgumentNullException("value", "Newline string may not be null");
                if (value.Equals("")) throw new ArgumentException("Newline may not be empty", "value");
                m_NewLine = value;
            }
        }
        #endregion

        #region Driver Settings
        /// <summary>
        /// Specify the driver In Queue at the time it is opened.
        /// </summary>
        /// <remarks>
        /// This provides the driver a recommended internal input buffer, in bytes. 
        /// </remarks>
        public int DriverInQueue
        {
            get { return m_SerialPort.DriverInQueue; }
            set
            {
                if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
                if (IsOpen) throw new InvalidOperationException("Serial Port already opened");
                m_SerialPort.DriverInQueue = value;
            }
        }

        /// <summary>
        /// Specify the driver Out Queue at the time it is opened.
        /// </summary>
        /// <remarks>
        /// This provides the driver a recommended internal output buffer, in bytes. 
        /// </remarks>
        public int DriverOutQueue
        {
            get { return m_SerialPort.DriverOutQueue; }
            set
            {
                if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
                if (IsOpen) throw new InvalidOperationException("Serial Port already opened");
                m_SerialPort.DriverOutQueue = value;
            }
        }
        #endregion

        /// <summary>
        /// Gets a value that determines whether the current stream can time out.
        /// </summary>
        /// <returns>A value that determines whether the current stream can time out.</returns>
        public override bool CanTimeout { get { return true; } }

        #region Reading
        /// <summary>
        /// Check if this stream supports reading.
        /// </summary>
        /// <remarks>
        /// Supported so long as the stream is not disposed.
        /// </remarks>
        public override bool CanRead
        {
            get { return !IsDisposed; }
        }


        private int m_ReadTimeout = -1;

        /// <summary>
        /// Define the timeout when reading data from the stream.
        /// </summary>
        /// <remarks>
        /// This defines the timeout when data arrives in the buffered memory of this
        /// stream, that is, when the driver indicates that data has arrived to the
        /// application.
        /// <para>Should the user perform a read operation and no data is available
        /// to copy in the buffer, a timeout will occur.</para>
        /// <para>Set this property to -1 for an infinite timeout.</para>
        /// </remarks>
        public override int ReadTimeout
        {
            get { return m_ReadTimeout; }
            set
            {
                if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
                if (value < 0) {
                    m_ReadTimeout = -1;
                } else {
                    m_ReadTimeout = value;
                }
            }
        }

        /// <summary>
        /// Gets or sets the size of the SerialPortStream input buffer.
        /// </summary>
        /// <remarks>
        /// Sets the amount of buffering to use when reading data from the serial port.
        /// Data is read locally into this buffered stream through another port.
        /// <para>The Microsoft implementation uses this to set the buffer size of the
        /// underlying driver. This implementation interprets the ReadBufferSize
        /// differently by setting the local buffer which can be much larger (megabytes)
        /// and independent of the low level driver.</para>
        /// </remarks>
        /// <exception cref="InvalidOperationException">An attempt was used to change
        /// the size of the buffer while the port is open (and therefore buffering is
        /// active).</exception>
        public int ReadBufferSize
        {
            get { return m_SerialPort.SerialPortIo.ReadBufferSize; }
            set
            {
                if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
                if (IsOpen) throw new InvalidOperationException("Serial Port already opened");
                m_SerialPort.SerialPortIo.ReadBufferSize = value;
                if (m_RxThreshold > value) m_RxThreshold = value;
            }
        }

        private int m_RxThreshold = 1;

        /// <summary>
        /// Gets or sets the number of bytes in the read buffer before a DataReceived event occurs.
        /// </summary>
        public int ReceivedBytesThreshold
        {
            get { return m_RxThreshold; }
            set
            {
                if (value <= 0) throw new ArgumentOutOfRangeException("value", "Must be a positive value (1 or greater)");
                if (value > m_SerialPort.SerialPortIo.ReadBufferSize)
                    throw new ArgumentOutOfRangeException("value", "Must be less or equal to the ReadBufferSize");

                // Only raise an event if we think that we wouldn't have received an event otherwise
                int btr = m_SerialPort.SerialPortIo.BytesToRead;
                if (btr < m_RxThreshold && btr > value) {
                    m_RxThreshold = value;
                    SerialPortIo_CommEvent(this,
                        new NativeSerialPort.CommOverlappedIo.CommEventArgs(NativeMethods.SerialEventMask.EV_RXCHAR));
                } else {
                    m_RxThreshold = value;
                }
            }
        }

#if DRIVERBUFFEREDBYTES
        /// <summary>
        /// Gets the number of bytes of data in the receive buffer and in the serial driver.
        /// </summary>
        /// <remarks>
        /// This method returns the number of bytes available in the input read buffer.
        /// Bytes that are cached by the driver itself are also (generally) accounted for.
        /// <para>A small error in determining the number of bytes available to read
        /// may occur, in particularly, an amount less than what is actually available
        /// for reading may be returned.</para>
        /// <para>Why we sometimes don't return the total number of bytes is due to
        /// the asynchronous behaviour of reading from the serial port with consideration
        /// to performance. Particularly, there might be a small time where the number
        /// of bytes in the cached buffer is "x", and the number of bytes in the serial
        /// driver is "y". Normally, the result will be the sum, "X+y". However, there
        /// may be a very small window of opportunity that the size of the buffer will
        /// be "x" and the number of bytes in the serial driver is zero, or less than
        /// "y", because it has just been transferred to the local buffer, but has not
        /// yet been accounted for.</para>
        /// </remarks>
        public int BytesToRead
        {
            get
            {
                if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
                return m_SerialPort.SerialPortIo.BytesToRead;
            }
        }
#else
        /// <summary>
        /// Gets the number of bytes of data in the receive buffer.
        /// </summary>
        /// <remarks>
        /// This method returns the number of bytes available in the input read buffer.
        /// Bytes that are cached by the driver itself are not accounted for, as they
        /// haven't yet been read by the local thread.
        /// <para>This has the effect, that if the local buffer is full (let's say that
        /// it is arbitrarily picked to be 64KB) and the local driver also has buffered
        /// 4KB, only the size of the local buffer is given, so 64KB (instead of the
        /// expected 68KB).</para>
        /// </remarks>
        public int BytesToRead
        {
            get
            {
                if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
                return m_SerialPort.SerialPortIo.BufferedBytesToRead;
            }
        }
#endif

        private void ReadCheck(byte[] buffer, int offset, int count)
        {
            if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
            if (buffer == null) throw new ArgumentNullException("buffer", "NULL buffer provided");
            if (offset < 0) throw new ArgumentOutOfRangeException("offset", "Negative offset provided");
            if (count < 0) throw new ArgumentOutOfRangeException("count", "Negative count provided");
            if (buffer.Length - offset < count) throw new ArgumentException("offset and count exceed buffer boundaries");
        }

        /// <summary>
        /// Read data from the buffered serial stream into the array provided.
        /// </summary>
        /// <param name="buffer">The buffer to receive the data.</param>
        /// <param name="offset">Offset into the buffer where to start putting the data.</param>
        /// <param name="count">Maximum number of bytes to read into the buffer.</param>
        /// <returns>The actual number of bytes copied into the buffer, 0 if there was a timeout.</returns>
        public override int Read(byte[] buffer, int offset, int count)
        {
            ReadCheck(buffer, offset, count);
            if (count == 0) return 0;
            return InternalBlockingRead(buffer, offset, count);
        }

        private int InternalBlockingRead(byte[] buffer, int offset, int count)
        {
            if (!m_SerialPort.SerialPortIo.WaitForReadEvent(m_ReadTimeout)) return 0;
            return InternalRead(buffer, offset, count);
        }

        private int InternalRead(byte[] buffer, int offset, int count)
        {
            int bytes = m_SerialPort.SerialPortIo.Read(buffer, offset, count);
            if (bytes > 0) ReadToReset();
            return bytes;
        }

        delegate int ReadDelegate(byte[] buffer, int offset, int count);

        /// <summary>
        /// Begins an asynchronous read operation.
        /// </summary>
        /// <param name="buffer">The buffer to read the data into.</param>
        /// <param name="offset">The byte offset in <paramref name="buffer"/> at which to begin writing data read from the stream.</param>
        /// <param name="count">The maximum number of bytes to read.</param>
        /// <param name="callback">An optional asynchronous callback, to be called when the read is complete.</param>
        /// <param name="state">A user-provided object that distinguishes this particular asynchronous read request from other requests.</param>
        /// <returns>An <see cref="System.IAsyncResult"/> object to be used with <see cref="EndRead"/>.</returns>
        public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
        {
            ReadCheck(buffer, offset, count);

            if (count == 0 || m_SerialPort.SerialPortIo.WaitForReadEvent(0)) {
                // Data in the buffer, we can return immediately
                LocalAsync<int> ar = new LocalAsync<int>(state);
                if (count > 0) {
                    ar.Result = InternalRead(buffer, offset, count);
                } else {
                    ar.Result = 0;
                }
                ar.IsCompleted = true;
                ar.CompletedSynchronously = true;
                if (callback != null) callback(ar);
                return ar;
            } else {
                // No data in buffer, so we create a thread in the background
                ReadDelegate read = InternalBlockingRead;
                return read.BeginInvoke(buffer, offset, count, callback, state);
            }
        }

        /// <summary>
        /// Waits for the pending asynchronous read to complete.
        /// </summary>
        /// <param name="asyncResult">The reference to the pending asynchronous request to finish.</param>
        /// <returns>The number of bytes read from the stream, between zero (0) and the number of bytes you requested.
        /// Streams return zero (0) only at the end of the stream, otherwise, they should block until at least one byte is available.</returns>
        public override int EndRead(IAsyncResult asyncResult)
        {
            if (asyncResult is LocalAsync<int>) {
                LocalAsync<int> ar = (LocalAsync<int>)asyncResult;
                if (!ar.IsCompleted) ar.AsyncWaitHandle.WaitOne(-1);
                ar.Dispose();
                return ar.Result;
            } else {
                AsyncResult ar = (AsyncResult)asyncResult;
                ReadDelegate caller = (ReadDelegate)ar.AsyncDelegate;
                return caller.EndInvoke(asyncResult);
            }
        }

        /// <summary>
        /// Reads a number of characters from the SerialPortStream input buffer and writes
        /// them into an array of characters at a given offset.
        /// </summary>
        /// <remarks>
        /// This function converts the data in the local buffer to characters based on the
        /// encoding defined by the encoding property. The encoder used may buffer data between
        /// calls if characters may require more than one byte of data for its interpretation
        /// as a character.
        /// </remarks>
        /// <param name="buffer">The character array to write the input to.</param>
        /// <param name="offset">Offset into the buffer where to start putting the data.</param>
        /// <param name="count">Maximum number of bytes to read into the buffer.</param>
        /// <returns>The actual number of bytes copied into the buffer, 0 if there was a timeout.</returns>
        public int Read(char[] buffer, int offset, int count)
        {
            if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
            if (buffer == null) throw new ArgumentNullException("buffer", "NULL buffer provided");
            if (offset < 0) throw new ArgumentOutOfRangeException("offset", "Negative offset provided");
            if (count < 0) throw new ArgumentOutOfRangeException("count", "Negative count provided");
            if (buffer.Length - offset < count) throw new ArgumentException("offset and count exceed buffer boundaries");
            if (count == 0) return 0;

            // Dealing with cached characters read by the ReadTo() method
            int cached = 0;
            ReadToOverflowCheck();
            if (IsReadToBuffered()) {
                int readlen = Math.Min(count, m_Read.Length - (m_ReadIncomplete ? 1 : 0));
                if (readlen > 0) {
                    cached = m_Read.CopyTo(buffer, offset, readlen);
                    ReadToConsume(cached);

                    if (cached == count) {
                        // Read operation is complete, may/may not be data in the cache
                        return cached;
                    }
                    offset += cached;
                    count -= cached;

                    // If there are no bytes in the serial driver, we don't need to wait
                    if (m_SerialPort.SerialPortIo.BufferedBytesToRead == 0) return cached;
                }
            }

            ReadToReset();

            // Obtaining bytes from our buffered I/O thread
            TimerExpiry t = new TimerExpiry(m_ReadTimeout);
            int timeout = m_ReadTimeout;
            do {
                if (!m_SerialPort.SerialPortIo.WaitForReadEvent(timeout)) return 0;
                int c = m_SerialPort.SerialPortIo.Read(buffer, offset, count, Decoder);
                if (c > 0) return c;
                timeout = t.RemainingTime();
            } while (timeout > 0);
            return 0;
        }

        /// <summary>
        /// Synchronously reads one byte from the SerialPort input buffer.
        /// </summary>
        /// <returns>The byte, cast to an Int32, or -1 if the end of the stream has been read.</returns>
        public override int ReadByte()
        {
            if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
            if (!m_SerialPort.SerialPortIo.WaitForReadEvent(m_ReadTimeout)) return -1;
            int value = m_SerialPort.SerialPortIo.ReadByte();
            if (value != -1) ReadToReset();
            return value;
        }

        /// <summary>
        /// Synchronously reads one character from the SerialPortStream input buffer.
        /// </summary>
        /// <returns>The character that was read. -1 indicates no data was available 
        /// within the timeout.</returns>
        public int ReadChar()
        {
            if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");

            // Dealing with cached characters read by the ReadTo() method
            ReadToOverflowCheck();
            if (IsReadToBuffered()) {
                // Check there is at least one valid byte. if the condition:
                // (m_ReadIncomplete == true && m_Read.Length == 1 || m_Read.Length < 1)
                // then there are no valid bytes available.
                if (!m_ReadIncomplete || m_Read.Length > 1) {
                    int value = m_Read[0];
                    ReadToConsume(1);
                    return value;
                }
            }

            ReadToReset();

            // Reading data from the byte buffer
            TimerExpiry t = new TimerExpiry(m_ReadTimeout);
            int timeout = m_ReadTimeout;
            do {
                if (!m_SerialPort.SerialPortIo.WaitForReadEvent(timeout)) return -1;
                int c = m_SerialPort.SerialPortIo.ReadChar(Decoder);
                if (c != -1) return c;
                timeout = t.RemainingTime();
            } while (timeout > 0);
            return -1;
        }

        /// <summary>
        /// Reads up to the NewLine value in the input buffer.
        /// </summary>
        /// <returns>The contents of the input buffer up to the first occurrence of
        /// a NewLine value.</returns>
        public string ReadLine()
        {
            if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
            return ReadTo(m_NewLine);
        }

        /// <summary>
        /// Reads a string up to the specified <i>text</i> in the input buffer.
        /// </summary>
        /// <remarks>
        /// The ReadTo() function will read text from the byte buffer up to a predetermined
        /// limit (1024 characters) when looking for the string <i>text</i>. If <i>text</i>
        /// is not found within this limit, data is thrown away and more data is read
        /// (effectively consuming the earlier bytes).
        /// <para>this method is provided as compatibility with the Microsoft implementation. 
        /// There are some important differences however. This method attempts to fix a minor
        /// pathological problem with the Microsoft implementation. If the string <i>text</i>
        /// is not found, the MS implementation may modify the internal state of the decoder.
        /// As a workaround, it pushes all decoded characters back into its internal byte
        /// buffer, which fixes the problem that a second call to the ReadTo() method returns
        /// the consistent results, but a call to Read(byte[], ..) may return data that was
        /// not actually transmitted by the DCE. This would happen in case that an invalid
        /// byte sequence was found, converted to a fallback character. The original byte
        /// sequence is removed and replaced with the byte equivalent of the fallback
        /// character.</para>
        /// <para>This method is rather slow, because it tries to preserve the byte buffer
        /// in case of failure. If your application works entirely by using character
        /// sequences, you may get much better performance by using Read(char[], ...) and
        /// searching for string sequences yourself, as Read(char[], ...) does a complete
        /// conversion bytes to characters in as few steps as possible.</para>
        /// </remarks>
        /// <param name="text">The text to indicate where the read operation stops.</param>
        /// <returns>The contents of the input buffer up to the specified <i>text</i>.</returns>
        public string ReadTo(string text)
        {
            if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
            if (string.IsNullOrEmpty(text)) throw new ArgumentException("Parameter text shall not be null or empty", "text");

            // Decoders in .NET are designed for streams and not really for reading
            // a little bit of data. By design, they are "greedy", they consume as much
            // byte data as possible. The data that they consume is cached internally.
            // Because it's not possible to ask the decoder only to decode if there
            // is sufficient bytes, we have to keep account of how many bytes are
            // consumed for each character.

            // Hence, m_ReadIncomplete tells us if we've consumed bytes, but have not
            // yet received a converted character.

            m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: BEGIN");

            bool changedtext = !text.Equals(m_ReadToString);
            if (changedtext) {
                // Last search might have had a read overflow and we're now looking for a different
                // string. So we need to reset our buffer and start from the beginning
                ReadToOverflowCheck();
            }

            int readlen = m_Read.Length - (m_ReadIncomplete ? 1 : 0);
            if (changedtext && readlen >= text.Length) {
                // Check if the text already exists
                string lbuffer = m_Read.GetString(readlen);
                int p = lbuffer.IndexOf(text, StringComparison.Ordinal);
                if (p != -1) {
                    // It does exist, so consume up to the buffered portion
                    string result = lbuffer.Substring(0, p);
                    int l = p + text.Length;
                    ReadToConsume(l);
                    return result;
                }
            }

            TimerExpiry te = new TimerExpiry(m_ReadTimeout);
            while (!ReadToMatch(text)) {
                int bytesRead;
                int c = m_SerialPort.SerialPortIo.PeekChar(m_ReadOffset, Decoder, out bytesRead);
                m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0,
                    "ReadTo: c = {0} = PeekChar(m_ReadOffset={1}, Decoder, out bytesRead={2})", c, m_ReadOffset, bytesRead);
                if (c != -1) {
                    if (m_ReadIncomplete) {
                        int lb = m_Read.Length - 1;
                        m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0,
                            "ReadTo: m_ReadIncomplete == true; m_Read[{0}]={1}; m_ReadOffsets[{0}]={2}", lb, (char)c, m_ReadOffsets[lb]);
                        m_Read[lb] = (char)c;
                        m_ReadOffsets[lb] += bytesRead;
                        m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0,
                            "ReadTo: m_ReadIncomplete == true; m_Read[{0}]={1}; m_ReadOffsets[{0}]={2}", lb, (char)c, m_ReadOffsets[lb]);
                    } else {
                        if (m_Read.Free == 0) {
                            m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: m_ReadIncomplete == false; m_Read.Free==0");

                            // Discard the oldest character in preference for the newer character
                            if (m_ReadOverflow == -1) {
                                // Overflow. We capture the first byte, because we'll have to reset the decoder later
                                m_ReadOverflowChar = m_Read[0];
                                m_ReadOverflow = m_ReadOffsets[0];
                            }
                            m_Read.Consume(1);
                            m_ReadOffsets.Consume(1);
                        }
                        m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0,
                            "ReadTo: m_ReadIncomplete == false; m_Read.Append({0}); m_ReadOffsets.Append({1})", (char)c, bytesRead);
                        m_Read.Append((char)c);
                        m_ReadOffsets.Append(bytesRead);
                    }
                    m_ReadOffset += bytesRead;
                    m_ReadIncomplete = false;
                    m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: m_ReadOffset = {0}", m_ReadOffset);
                } else {
                    if (bytesRead > 0) {
                        if (!m_ReadIncomplete) {
                            // The decoder consumed bytes, even though we don't have a character
                            if (m_Read.Free == 0) {
                                m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: m_ReadIncomplete == false; m_Read.Free == 0");
                                m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: m_Read.Consume(1)");
                                m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: m_ReadOffset = {0} - {1}", m_ReadOffset, m_ReadOffsets[0]);

                                // Discard the oldest character in preference for the newer character
                                if (m_ReadOverflow == -1) {
                                    // Overflow. We capture the first byte, because we'll have to reset the decoder later
                                    m_ReadOverflowChar = m_Read[0];
                                    m_ReadOverflow = m_ReadOffsets[0];
                                }
                                m_Read.Consume(1);
                                m_ReadOffsets.Consume(1);

                                m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: m_ReadOffset = {0}", m_ReadOffset);
                            }
                            m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: m_ReadIncomplete == false; m_Read.Free == {0}", m_Read.Free);
                            m_Read.Append((char)0);
                            m_ReadOffsets.Append(bytesRead);
                            m_ReadOffset += bytesRead;
                            m_ReadIncomplete = true;
                        } else {
                            int lb = m_Read.Length - 1;
                            m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0,
                                "ReadTo: m_ReadIncomplete == true; m_ReadOffsets[{0}]={1}", lb, m_ReadOffsets[lb]);
                            m_ReadOffsets[lb] += bytesRead;
                            m_ReadOffset += bytesRead;
                            m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0,
                                "ReadTo: m_ReadIncomplete == true; m_ReadOffsets[{0}]={1}", lb, m_ReadOffsets[lb]);
                        }
                    } else {
                        m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0,
                            "ReadTo: WaitForReadEvent({0}, {1})", m_ReadOffset + 1, te.RemainingTime());
                        // Need to wait for the next byte
                        if (!m_SerialPort.SerialPortIo.WaitForReadEvent(m_ReadOffset + 1, te.RemainingTime())) {
                            m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: TIMEOUT");
                            // Remember the string for the next timeout
                            m_ReadToString = text;
                            throw new TimeoutException();
                        }
                    }
                }
            }

            m_SerialPort.SerialPortIo.DiscardInBuffer(m_ReadOffset);
            string rbuffer = m_Read.GetString();
            rbuffer = rbuffer.Substring(0, m_Read.Length - text.Length);
            ReadToReset();
            m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: RESULT = {0}", rbuffer);
            return rbuffer;
        }

        /// <summary>
        /// Given a string, compare if there is a match at the end.
        /// </summary>
        /// <param name="text">Text to compare.</param>
        /// <returns><b>true</b> if there is a match, <b>false</b> otherwise.</returns>
        private bool ReadToMatch(string text)
        {
            int bl = m_Read.Length;
            int offset = bl - text.Length;
            if (offset < 0) return false;

            for (int i = 0; i < text.Length; i++) {
                if (m_Read[i + offset] != text[i]) return false;
            }
            return true;
        }

        /// <summary>
        /// Indicates if the ReadTo buffer is in use.
        /// </summary>
        /// <returns><b>true</b> if there is data cached in the ReadTo buffer.</returns>
        private bool IsReadToBuffered()
        {
            return m_ReadOffset > 0;
        }

        /// <summary>
        /// Reset the buffers in ReadTo() to start from the beginning.
        /// </summary>
        private void ReadToReset()
        {
            if (m_ReadOffset != 0) {
                if (m_Decoder != null) m_Decoder.Reset();
                m_Read.Reset();
                m_ReadOffsets.Reset();
                m_ReadOffset = 0;
                m_ReadIncomplete = false;
                m_ReadOverflow = -1;
                m_ReadToString = null;
            }
        }

        /// <summary>
        /// Consume characters from the cache and update the byte offsets.
        /// </summary>
        /// <param name="chars">Number of characters to consume.</param>
        /// <returns>Number of bytes consumed.</returns>
        private int ReadToConsume(int chars)
        {
            // ReadToConsume doesn't work if we've had a read overflow.
            // That's because we don't know how many bytes to consume as
            // data has been lost.
            System.Diagnostics.Debug.Assert(m_ReadOverflow == -1);

            int b = 0;
            for (int i = 0; i < chars; i++) b += m_ReadOffsets[i];
            m_SerialPort.SerialPortIo.DiscardInBuffer(b);
            m_ReadOffset -= b;
            m_Read.Consume(chars);
            m_ReadOffsets.Consume(chars);
            return b;
        }

        /// <summary>
        /// Resets the ReadTo buffer to a valid state in case an overflow occurred.
        /// </summary>
        /// <remarks>
        /// This method should be called by all methods that need to put the ReadTo buffer
        /// in a consistent state for reading the next character. Pretty much all functions
        /// that work with characters need to use this method, except the ReadTo() method
        /// itself under the condition that the string to ReadTo() is the same as before.
        /// </remarks>
        private void ReadToOverflowCheck()
        {
            if (m_ReadOverflow != -1) {
                m_Read.Reset();
                m_ReadOffsets.Reset();
                m_Read.Append(m_ReadOverflowChar);
                m_ReadOffsets.Append(m_ReadOverflow);
                m_ReadOffset = m_ReadOverflow;
                m_ReadOverflow = -1;

                Decoder.Reset();
                m_ReadIncomplete = false;
            }
        }

        /// <summary>
        /// Reads all immediately available bytes.
        /// </summary>
        /// <remarks>
        /// Reads all data in the current buffer. If there is no data available, then no data
        /// is returned. This is different to the Microsoft implementation, that will read all
        /// data, and if there is no data, then it waits for data based on the timeouts. This
        /// method employs no timeouts.
        /// <para>Because this method returns only the data that is currently in the cached
        /// buffer and ignores the data that is actually buffered by the driver itself, 
        /// there may be a slight discrepancy between the value returned by BytesToRead and the
        /// actual length of the string returned.</para>
        /// <para>This method differs slightly from the Microsoft implementation in that this
        /// function doesn't initiate a read operation, as we have a dedicated thread to reading
        /// data that is running independently.</para>
        /// </remarks>
        /// <returns>The contents of the stream and the input buffer of the SerialPortStream.</returns>
        public string ReadExisting()
        {
            if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");

            StringBuilder sb = new StringBuilder();
            char[] c = new char[512];

            // Take into account the ReadTo buffer
            ReadToOverflowCheck();
            sb.Append(m_Read.GetString());
            ReadToReset();

            int b = m_SerialPort.SerialPortIo.BufferedBytesToRead;
            while (b > 0) {
                int bytesUsed;
                int cr = m_SerialPort.SerialPortIo.Read(c, 0, c.Length, Decoder, out bytesUsed);
                b -= bytesUsed;
                sb.Append(c, 0, cr);

                // Just in case there was an error and we didn't consume any bytes. Technically an
                // error if this condition occurs.
                if (bytesUsed == 0) {
                    m_Trace.TraceEvent(System.Diagnostics.TraceEventType.Warning, 0, "Unexpected condition in ReadExisting(): " +
                        "Expected {0} bytes remaining to read", b);
                    return sb.ToString();
                }
            }
            return sb.ToString();
        }

        /// <summary>
        /// Discards data from the serial driver's receive buffer.
        /// </summary>
        /// <remarks>
        /// This function will discard the receive buffer of the SerialPortStream. Any
        /// data pending in hardware cannot be discarded.
        /// </remarks>
        public void DiscardInBuffer()
        {
            if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
            m_SerialPort.SerialPortIo.DiscardInBuffer();
        }
        #endregion

        #region Writing
        /// <summary>
        /// Check if this stream supports writing.
        /// </summary>
        /// <remarks>
        /// Supported so long as the stream is not disposed.
        /// </remarks>
        public override bool CanWrite
        {
            get { return IsOpen; }
        }

        private int m_WriteTimeout = -1;

        /// <summary>
        /// Define the timeout when writing data to the local buffer.
        /// </summary>
        /// <remarks>
        /// This defines the timeout when writing data to the local buffer.
        /// No guarantees are given to when the data will actually be transferred
        /// over to the serial port as this is dependent on the hardware configuration
        /// and flow control.
        /// <para>When writing data to the stream buffer, a timeout will
        /// occur if not all data can be written to the local buffer and the buffer
        /// wasn't able to empty itself in the period given by the timeout.</para>
        /// <para>Naturally then, this depends on the size of the send buffer
        /// in use, how much data is already in the buffer, how fast the data
        /// can leave the buffer.</para>
        /// <para>In case the data cannot be written to the buffer in the given
        /// timeout, no data will be written at all.</para>
        /// </remarks>
        public override int WriteTimeout
        {
            get { return m_WriteTimeout; }
            set
            {
                if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
                if (value < 0) {
                    m_WriteTimeout = -1;
                } else {
                    m_WriteTimeout = value;
                }
            }
        }

        /// <summary>
        /// Gets or sets the size of the serial port output buffer. 
        /// </summary>
        /// <remarks>
        /// Defines the size of the buffered stream write buffer, used to send data
        /// to the serial port. It does not affect the buffers in the serial port 
        /// hardware itself.
        /// <para>The Microsoft implementation uses this to set the buffer size of the
        /// underlying driver. This implementation interprets the WriteBufferSize
        /// differently by setting the local buffer which can be much larger (megabytes)
        /// and independent of the low level driver.</para>
        /// </remarks>
        /// <exception cref="InvalidOperationException">An attempt was used to change
        /// the size of the buffer while the port is open (and therefore buffering is
        /// active).</exception>
        public int WriteBufferSize
        {
            get { return m_SerialPort.SerialPortIo.WriteBufferSize; }
            set
            {
                if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
                if (IsOpen) throw new InvalidOperationException("Serial Port already opened");
                m_SerialPort.SerialPortIo.WriteBufferSize = value;
            }
        }

        /// <summary>
        /// Gets the number of bytes of data in the send buffer.
        /// </summary>
        /// <remarks>
        /// The send buffer includes the serial driver's send buffer as well as internal
        /// buffering in the SerialPortStream itself.
        /// </remarks>
        public int BytesToWrite
        {
            get
            {
                if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
                return m_SerialPort.SerialPortIo.BytesToWrite;
            }
        }

        /// <summary>
        /// Wait for all data to be written over the serial port.
        /// </summary>
        /// <remarks>
        /// This function will block until all data has been written to the serial
        /// port. That is, the buffered serial stream is emptied and the hardware
        /// is checked that no data to write is pending.
        /// </remarks>
        public override void Flush()
        {
            if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
            m_SerialPort.SerialPortIo.WaitForWriteEmptyEvent(m_WriteTimeout);
        }

        private bool WriteCheck(byte[] buffer, int offset, int count)
        {
            if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
            if (buffer == null) throw new ArgumentNullException("buffer", "NULL buffer provided");
            if (offset < 0) throw new ArgumentOutOfRangeException("offset", "Negative offset provided");
            if (count < 0) throw new ArgumentOutOfRangeException("count", "Negative count provided");
            if (buffer.Length - offset < count) throw new ArgumentException("offset and count exceed buffer boundaries");
            if (count == 0) return false;

            if (!m_SerialPort.SerialPortIo.IsRunning) {
                throw new InvalidOperationException("Serial I/O Thread not running");
            }

            // Check that count is less than the total size of the buffer, else raise
            // an exception immediately that the local buffer is too small.
            if (count > m_SerialPort.SerialPortIo.WriteBufferSize) {
                throw new InvalidOperationException("Insufficient buffer for the data requested");
            }
            return true;
        }

        /// <summary>
        /// Write the given data into the buffered serial stream for sending over the serial port.
        /// </summary>
        /// <remarks>
        /// Data is copied from the array provided into the local stream buffer. It does
        /// not guarantee that data will be sent over the serial port. So long as there is
        /// enough local buffer space to accept the write of count bytes, this function
        /// will succeed. In case that the buffered serial stream doesn't have enough data,
        /// the function will wait up to <c>WriteTimeout</c> milliseconds for enough
        /// buffer data to become available. In case that there is not enough space before
        /// the write timeout expires, no data is copied to the local stream and the
        /// function fails with an exception.
        /// <para>For reliability, this function will only write data to the write buffer
        /// if the complete set of data requested can be written. This implies that the
        /// parameter <b>count</b> be less or equal to the number of bytes that are
        /// available in the write buffer. Equivalently, you must make sure that you
        /// have a write buffer with at least <b>count</b> allocated bytes or this
        /// function will always raise an exception.</para>
        /// </remarks>
        /// <param name="buffer">The buffer containing data to send.</param>
        /// <param name="offset">Offset into the array buffer where data begins.</param>
        /// <param name="count">Number of bytes to copy into the local buffer.</param>
        /// <exception cref="TimeoutException">Not enough buffer space was made available
        /// before the timeout expired.</exception>
        public override void Write(byte[] buffer, int offset, int count)
        {
            if (!WriteCheck(buffer, offset, count)) return;
            if (count == 0) return;
            InternalBlockingWrite(buffer, offset, count);
        }

        private void InternalBlockingWrite(byte[] buffer, int offset, int count)
        {
            if (!m_SerialPort.SerialPortIo.WaitForWriteEvent(count, m_WriteTimeout)) {
                throw new TimeoutException("Couldn't write into buffer");
            }
            InternalWrite(buffer, offset, count);
        }

        private void InternalWrite(byte[] buffer, int offset, int count)
        {
            m_SerialPort.SerialPortIo.Write(buffer, offset, count);
        }

        delegate void WriteDelegate(byte[] buffer, int offset, int count);

        /// <summary>
        /// Begins an asynchronous write operation.
        /// </summary>
        /// <param name="buffer">The buffer to write data from.</param>
        /// <param name="offset">The byte offset in buffer from which to begin writing.</param>
        /// <param name="count">The maximum number of bytes to write.</param>
        /// <param name="callback">An optional asynchronous callback, to be called when the write is complete.</param>
        /// <param name="state">A user-provided object that distinguishes this particular asynchronous write request from other requests.</param>
        /// <returns>An IAsyncResult that represents the asynchronous write, which could still be pending.</returns>
        public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
        {
            WriteCheck(buffer, offset, count);

            if (count == 0 || m_SerialPort.SerialPortIo.WaitForWriteEvent(count, m_WriteTimeout)) {
                LocalAsync ar = new LocalAsync(state);
                if (count > 0) {
                    InternalWrite(buffer, offset, count);
                }
                ar.IsCompleted = true;
                ar.CompletedSynchronously = true;
                if (callback != null) callback(ar);
                return ar;
            } else {
                WriteDelegate write = InternalBlockingWrite;
                return write.BeginInvoke(buffer, offset, count, callback, state);
            }
        }

        /// <summary>
        /// Ends an asynchronous write operation.
        /// </summary>
        /// <param name="asyncResult">A reference to the outstanding asynchronous I/O request.</param>
        /// <remarks>
        /// EndWrite must be called exactly once on every IAsyncResult from BeginWrite.
        /// </remarks>
        public override void EndWrite(IAsyncResult asyncResult)
        {
            if (asyncResult is LocalAsync) {
                LocalAsync ar = (LocalAsync)asyncResult;
                if (!ar.IsCompleted) ar.AsyncWaitHandle.WaitOne(-1);
                ar.Dispose();
            } else {
                AsyncResult ar = (AsyncResult)asyncResult;
                WriteDelegate caller = (WriteDelegate)ar.AsyncDelegate;
                caller.EndInvoke(asyncResult);
            }
        }

        /// <summary>
        /// Writes a specified number of characters to the serial port using data from a buffer.
        /// </summary>
        /// <param name="buffer">The buffer containing data to send.</param>
        /// <param name="offset">Offset into the array buffer where data begins.</param>
        /// <param name="count">Number of characters to copy into the local buffer.</param>
        public void Write(char[] buffer, int offset, int count)
        {
            if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");

            if (!m_SerialPort.SerialPortIo.IsRunning) {
                throw new InvalidOperationException("Serial I/O Thread not running");
            }

            int bytes = Encoding.GetByteCount(buffer, offset, count);
            if (bytes > m_SerialPort.SerialPortIo.WriteBufferSize) {
                throw new InvalidOperationException("Insufficient buffer for the data requested");
            }
            if (!m_SerialPort.SerialPortIo.WaitForWriteEvent(bytes, m_WriteTimeout)) {
                throw new TimeoutException("Couldn't write into buffer");
            }

            byte[] bbuffer = Encoding.GetBytes(buffer, offset, count);
            m_SerialPort.SerialPortIo.Write(bbuffer, 0, bbuffer.Length);
        }

        /// <summary>
        /// Writes the specified string to the serial port.
        /// </summary>
        /// <param name="text">The string for output.</param>
        public void Write(string text)
        {
            if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
            if (!m_SerialPort.SerialPortIo.IsRunning) {
                throw new InvalidOperationException("Serial I/O Thread not running");
            }

            int bytes = Encoding.GetByteCount(text);
            if (bytes > m_SerialPort.SerialPortIo.WriteBufferSize) {
                throw new InvalidOperationException("Insufficient buffer for the data requested");
            }
            if (!m_SerialPort.SerialPortIo.WaitForWriteEvent(bytes, m_WriteTimeout)) {
                throw new TimeoutException("Couldn't write into buffer");
            }

            byte[] bbuffer = Encoding.GetBytes(text);
            m_SerialPort.SerialPortIo.Write(bbuffer, 0, bbuffer.Length);
        }

        /// <summary>
        /// Writes the specified string and the NewLine value to the output buffer.
        /// </summary>
        /// <param name="text">The string to write to the output buffer.</param>
        public void WriteLine(string text)
        {
            if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
            Write(text + m_NewLine);
        }

        /// <summary>
        /// Discards data from the serial driver's transmit buffer.
        /// </summary>
        /// <remarks>
        /// Clears the local buffer for data not yet sent to the serial port, as well as
        /// attempting to clear the buffers in the driver itself.
        /// </remarks>
        public void DiscardOutBuffer()
        {
            if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
            m_SerialPort.SerialPortIo.DiscardOutBuffer();
        }
        #endregion

        #region Modem Information and Serial State
        /// <summary>
        /// Gets the state of the Carrier Detect line for the port.
        /// </summary>
        /// <remarks>
        /// This property can be used to monitor the state of the carrier detection
        /// line for a port. No carrier usually indicates that the receiver has hung
        /// up and the carrier has been dropped.
        /// <para>Windows documentation sometimes refers to the Carrier Detect line
        /// as the RLSD (Receive Line Signal Detect).</para>
        /// </remarks>
        public bool CDHolding
        {
            get
            {
                if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
                if (!IsOpen) throw new InvalidOperationException("Serial Port not opened");
                m_SerialPort.SerialPortModemStatus.GetCommModemStatus();
                return m_SerialPort.SerialPortModemStatus.Rlsd;
            }
        }

        /// <summary>
        /// Gets the state of the Clear-to-Send line.
        /// </summary>
        /// <remarks>
        /// The Clear-to-Send (CTS) line is used in Request to Send/Clear to Send
        /// (RTS/CTS) hardware handshaking. The CTS line is queried by a port before
        /// data is sent.
        /// </remarks>
        public bool CtsHolding
        {
            get
            {
                if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
                if (!IsOpen) throw new InvalidOperationException("Serial Port not opened");
                m_SerialPort.SerialPortModemStatus.GetCommModemStatus();
                return m_SerialPort.SerialPortModemStatus.Cts;
            }
        }

        /// <summary>
        /// Gets the state of the Data Set Ready (DSR) signal.
        /// </summary>
        /// <remarks>
        /// This property is used in Data Set Ready/Data Terminal Ready (DSR/DTR) handshaking.
        /// The Data Set Ready (DSR) signal is usually sent by a modem to a port to indicate that
        /// it is ready for data transmission or data reception.
        /// </remarks>
        public bool DsrHolding
        {
            get
            {
                if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
                if (!IsOpen) throw new InvalidOperationException("Serial Port not opened");
                m_SerialPort.SerialPortModemStatus.GetCommModemStatus();
                return m_SerialPort.SerialPortModemStatus.Dsr;
            }
        }

        /// <summary>
        /// Gets the state of the Ring line signal.
        /// </summary>
        /// <remarks>
        /// The ring line is a separate line from a modem that indicates if there is an incoming
        /// call.
        /// </remarks>
        public bool RingHolding
        {
            get
            {
                if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
                if (!IsOpen) throw new InvalidOperationException("Serial Port not opened");
                m_SerialPort.SerialPortModemStatus.GetCommModemStatus();
                return m_SerialPort.SerialPortModemStatus.Ring;
            }
        }
        #endregion

        #region Serial Configuration Settings
        /// <summary>
        /// Gets or sets the serial baud rate.
        /// </summary>
        /// <remarks>
        /// The stream doesn't impose any arbitrary limits on setting the baud rate. It is passed
        /// directly to the driver and it is up to the driver to determine if the baud rate is
        /// settable or not. Normally, a driver will attempt to set a baud rate that is within 5%
        /// of the requested baud rate (but not guaranteed). 
        /// <para>If the serial driver doesn't support setting the baud rate, setting this property
        /// is silently ignored and the baud rate isn't updated.</para>
        /// </remarks>
        public int BaudRate
        {
            get
            {
                if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
                return m_SerialPort.SerialPortCommState.BaudRate;
            }
            set
            {
                if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
                m_SerialPort.SerialPortCommState.BaudRate = value;
                if (IsOpen) m_SerialPort.SerialPortCommState.SetCommState();
            }
        }

        /// <summary>
        /// Gets or sets the standard length of data bits per byte.
        /// </summary>
        /// <remarks>
        /// The range of values for this property is from 5 through 8 and 16. A check is made by setting
        /// this property against the advertised capabilities of the driver. That is, a driver lists
        /// its capabilities to say what byte sizes it can support. If the driver cannot support the byte
        /// size requested, an exception is raised.
        /// <para>Not all possible combinations are allowed by all drivers. That implies, that an exception
        /// may be raised for a valid setting of the DataBits property, if the other parameters are not
        /// valid. Such an example might be that 5-bits are only supported with 2 stop bits and not otherwise.
        /// The driver itself will raise an exception to the application in this case.</para>
        /// <para>If the serial driver doesn't support setting the data bits, setting this property is silently ignored
        /// and the number of data bits isn't updated.</para>
        /// </remarks>
        public int DataBits
        {
            get
            {
                if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
                return m_SerialPort.SerialPortCommState.ByteSize;
            }
            set
            {
                if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
                m_SerialPort.SerialPortCommState.ByteSize = value;
                if (IsOpen) m_SerialPort.SerialPortCommState.SetCommState();
            }
        }

        /// <summary>
        /// Gets or sets the standard number of stop bits per byte.
        /// </summary>
        /// <remarks>
        /// Gets or sets the stop bits that should be used when transmitting and receiving data over the serial
        /// port. If the serial driver doesn't support setting the stop bits, setting this property is silently ignored
        /// and the number of stop bits isn't updated.
        /// </remarks>
        public StopBits StopBits
        {
            get
            {
                if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
                return m_SerialPort.SerialPortCommState.StopBits;
            }
            set
            {
                if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
                if (!Enum.IsDefined(typeof(StopBits), value))
                    throw new ArgumentOutOfRangeException("value", "Unknown setting for StopBits");

                m_SerialPort.SerialPortCommState.StopBits = value;
                if (IsOpen) m_SerialPort.SerialPortCommState.SetCommState();
            }
        }

        /// <summary>
        /// Gets or sets the parity-checking protocol.
        /// </summary>
        /// <remarks>
        /// Parity is an error-checking procedure in which the number of 1s must always be the same — either
        /// even or odd — for each group of bits that is transmitted without error. In modem-to-modem
        /// communications, parity is often one of the parameters that must be agreed upon by sending parties
        /// and receiving parties before transmission can take place.
        /// </remarks>
        public Parity Parity
        {
            get
            {
                if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
                if (m_SerialPort.SerialPortCommState.ParityEnable) return m_SerialPort.SerialPortCommState.Parity;
                return Parity.None;
            }
            set
            {
                if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
                if (!Enum.IsDefined(typeof(Parity), value))
                    throw new ArgumentOutOfRangeException("value", "Unknown setting for Parity");

                Parity parity = m_SerialPort.SerialPortCommState.Parity;
                bool parityEnabled = m_SerialPort.SerialPortCommState.ParityEnable;
                byte errorChar = m_SerialPort.SerialPortCommState.ErrorChar;
                bool errorEnabled = m_SerialPort.SerialPortCommState.ErrorCharEnabled;

                if (value == Parity.None) {
                    m_SerialPort.SerialPortCommState.ParityEnable = false;
                    m_SerialPort.SerialPortCommState.Parity = Parity.None;
                    m_SerialPort.SerialPortCommState.ErrorCharEnabled = false;
                    m_SerialPort.SerialPortCommState.ErrorChar = 0;
                } else {
                    m_SerialPort.SerialPortCommState.ParityEnable = true;
                    m_SerialPort.SerialPortCommState.Parity = value;
                    if (m_ParityReplace == 0) {
                        m_SerialPort.SerialPortCommState.ErrorCharEnabled = false;
                        m_SerialPort.SerialPortCommState.ErrorChar = 0;
                    } else {
                        m_SerialPort.SerialPortCommState.ErrorCharEnabled = true;
                        m_SerialPort.SerialPortCommState.ErrorChar = m_ParityReplace;
                    }
                }

                if (IsOpen) {
                    try {
                        m_SerialPort.SerialPortCommState.SetCommState();
                    } catch {
                        m_SerialPort.SerialPortCommState.ParityEnable = parityEnabled;
                        m_SerialPort.SerialPortCommState.Parity = parity;
                        m_SerialPort.SerialPortCommState.ErrorCharEnabled = errorEnabled;
                        m_SerialPort.SerialPortCommState.ErrorChar = errorChar;
                        throw;
                    }
                }
            }
        }

        /// <summary>
        /// Gets or sets the byte that replaces invalid bytes in a data stream when a parity error occurs.
        /// </summary>
        /// <remarks>
        /// If the value is set to the null character, parity replacement is disabled. This property
        /// only has an effect if the Parity property is not Parity.None.
        /// </remarks>
        public byte ParityReplace
        {
            get
            {
                if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
                return m_ParityReplace;
            }
            set
            {
                if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");

                m_ParityReplace = value;
                if (Parity != Parity.None) {
                    if (value == 0) {
                        m_SerialPort.SerialPortCommState.ErrorCharEnabled = false;
                        m_SerialPort.SerialPortCommState.ErrorChar = 0;
                    } else {
                        m_SerialPort.SerialPortCommState.ErrorCharEnabled = true;
                        m_SerialPort.SerialPortCommState.ErrorChar = m_ParityReplace;
                    }
                }
                if (IsOpen) m_SerialPort.SerialPortCommState.SetCommState();
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether null bytes are ignored when transmitted
        /// between the port and the receive buffer.
        /// </summary>
        /// <remarks>
        /// This value should normally be set to false, especially for binary transmissions. Setting
        /// this property to true can cause unexpected results for UTF32- and UTF16-encoded bytes.
        /// </remarks>
        public bool DiscardNull
        {
            get { return m_SerialPort.SerialPortCommState.Null; }
            set
            {
                if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
                m_SerialPort.SerialPortCommState.Null = value;
                if (IsOpen) m_SerialPort.SerialPortCommState.SetCommState();
            }
        }

        /// <summary>
        /// Gets or sets a value that enables the Data Terminal Ready (DTR) signal
        /// during serial communication.
        /// </summary>
        /// <remarks>
        /// Data Terminal Ready (DTR) is typically enabled during XON/XOFF software handshaking and
        /// Request to Send/Clear to Send (RTS/CTS) hardware handshaking, and modem communications. 
        /// </remarks>
        public bool DtrEnable
        {
            get
            {
                if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
                if (!IsOpen) throw new InvalidOperationException("Serial Port not opened");
                if (m_SerialPort.SerialPortCommState.DtrControl == NativeSerialPort.DtrControl.Handshake)
                    throw new InvalidOperationException("Serial Port set for DTR/DTS handshaking");

                if (m_SerialPort.SerialPortCommState.DtrControl == NativeSerialPort.DtrControl.Disable) return false;
                return true;
            }
            set
            {
                if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
                if (!IsOpen) throw new InvalidOperationException("Serial Port not opened");
                if (m_SerialPort.SerialPortCommState.DtrControl == NativeSerialPort.DtrControl.Handshake)
                    throw new InvalidOperationException("Serial Port set for DTR/DTS handshaking");

                m_SerialPort.SerialPortCommState.DtrControl = value ? NativeSerialPort.DtrControl.Enable : NativeSerialPort.DtrControl.Disable;
                m_SerialPort.SerialPortModemStatus.SetDtr(value);
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the Request to Send (RTS) signal is
        /// enabled during serial communication.
        /// </summary>
        /// <remarks>
        /// The Request to Transmit (RTS) signal is typically used in Request to Send/Clear
        /// to Send (RTS/CTS) hardware handshaking. 
        /// <para>Note, the windows feature RTS_CONTROL_TOGGLE is not supported by this class.</para>
        /// </remarks>
        public bool RtsEnable
        {
            get
            {
                if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
                if (!IsOpen) throw new InvalidOperationException("Serial Port not opened");
                if (m_SerialPort.SerialPortCommState.RtsControl == NativeSerialPort.RtsControl.Handshake)
                    throw new InvalidOperationException("Serial Port set for RTS/CTS handshaking");

                if (m_SerialPort.SerialPortCommState.RtsControl == NativeSerialPort.RtsControl.Disable) return false;
                return true;
            }
            set
            {
                if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
                if (!IsOpen) throw new InvalidOperationException("Serial Port not opened");
                if (m_SerialPort.SerialPortCommState.RtsControl == NativeSerialPort.RtsControl.Handshake)
                    throw new InvalidOperationException("Serial Port set for RTS/CTS handshaking");

                m_SerialPort.SerialPortCommState.RtsControl = value ? NativeSerialPort.RtsControl.Enable : NativeSerialPort.RtsControl.Disable;
                m_SerialPort.SerialPortModemStatus.SetRts(value);
            }
        }

        /// <summary>
        /// Gets or sets the handshaking protocol for serial port transmission of data.
        /// </summary>
        /// <remarks>
        /// Enables handshaking on the serial port. We support three types of handshaking:
        /// RTS/CTS; XON/XOFF; and DTR/DTS. The Microsoft implementation SerialPort only supports
        /// RTS/CTS and XON/XOFF.
        /// <para>This method is not 100% compatible with the Microsoft implementation. By disabling
        /// RTS flow control, it sets behaviour so that the RTS line is enabled. You must set the
        /// property RtsEnable as appropriate. Although the Microsoft implementation doesn't support
        /// DSR/DTR at all, setting this property will also set the DTR line to enabled. You must
        /// set the property DtrEnable as appropriate.</para>
        /// <para>When enabling DTR flow control, the DsrSensitivity flag is set, so the driver 
        /// ignores any bytes received, unless the DSR modem input line is high. Otherwise, if DTR
        /// flow control is disabled, the DSR line is ignored. For more detailed information about
        /// how windows works with flow control, see the site:
        /// http://msdn.microsoft.com/en-us/library/ms810467.aspx. When DTR flow control is specified,
        /// the fOutxDsrFlow is set along with fDsrSenstivity.</para>
        /// <para>Note, the windows feature RTS_CONTROL_TOGGLE is not supported by this class. This
        /// is also not supported by the Microsoft implementation.</para>
        /// </remarks>
        public Handshake Handshake
        {
            get
            {
                Handshake handshake = Handshake.None;
                if (m_SerialPort.SerialPortCommState.RtsControl == NativeSerialPort.RtsControl.Handshake) handshake |= Handshake.Rts;
                if (m_SerialPort.SerialPortCommState.DtrControl == NativeSerialPort.DtrControl.Handshake) handshake |= Handshake.Dtr;
                if (m_SerialPort.SerialPortCommState.InX) handshake |= Handshake.XOn;
                return handshake;
            }
            set
            {
                bool rts = (value & Handshake.Rts) != 0;
                m_SerialPort.SerialPortCommState.OutCtsFlow = rts;
                m_SerialPort.SerialPortCommState.RtsControl = rts ? NativeSerialPort.RtsControl.Handshake : NativeSerialPort.RtsControl.Enable;
                if (IsOpen && !rts) m_SerialPort.SerialPortModemStatus.SetRts(true);

                bool dtr = (value & Handshake.Dtr) != 0;
                m_SerialPort.SerialPortCommState.OutDsrFlow = dtr;
                m_SerialPort.SerialPortCommState.DsrSensitivity = dtr;
                m_SerialPort.SerialPortCommState.DtrControl = dtr ? NativeSerialPort.DtrControl.Handshake : NativeSerialPort.DtrControl.Enable;
                if (IsOpen && !dtr) m_SerialPort.SerialPortModemStatus.SetDtr(true);

                bool xon = (value & Handshake.XOn) != 0;
                m_SerialPort.SerialPortCommState.InX = xon;
                m_SerialPort.SerialPortCommState.OutX = xon;

                if (IsOpen) m_SerialPort.SerialPortCommState.SetCommState();
            }
        }

        /// <summary>
        /// Define the limit of actual bytes in the transmit buffer when XON is sent.
        /// </summary>
        /// <remarks>
        /// The XOFF character (19 or ^S) is sent when the input buffer comes within XoffLimit bytes of being full,
        /// and the XON character (17 or ^Q) is sent when the input buffer comes within XonLimit bytes of being empty.
        /// </remarks>
        public int XOnLimit
        {
            get { return m_SerialPort.SerialPortCommState.XonLim; }
            set
            {
                m_SerialPort.SerialPortCommState.XonLim = value;
                if (IsOpen) m_SerialPort.SerialPortCommState.SetCommState();
            }
        }

        /// <summary>
        /// Define the limit of free bytes in the buffer before XOFF is sent.
        /// </summary>
        /// <remarks>
        /// The XOFF character (19 or ^S) is sent when the input buffer comes within XoffLimit bytes of being full,
        /// and the XON character (17 or ^Q) is sent when the input buffer comes within XonLimit bytes of being empty.
        /// </remarks>
        public int XOffLimit
        {
            get { return m_SerialPort.SerialPortCommState.XoffLim; }
            set
            {
                m_SerialPort.SerialPortCommState.XoffLim = value;
                if (IsOpen) m_SerialPort.SerialPortCommState.SetCommState();
            }
        }

        /// <summary>
        /// Enable or Disable transmit of data when software flow control is enabled.
        /// </summary>
        /// <remarks>
        /// MSDN documentation states this flag as follows:
        /// <para>If this member is TRUE, transmission continues after the input buffer 
        /// has come within XoffLim bytes of being full and the driver has transmitted 
        /// the XoffChar character to stop receiving bytes. If this member is FALSE, 
        /// transmission does not continue until the input buffer is within XonLim bytes 
        /// of being empty and the driver has transmitted the XonChar character to 
        /// resume reception.</para>
        /// <para>When the driver buffer fills up and sends the XOFF character (and
        /// software flow control is active), this property defines if the driver should
        /// continue to send data over the serial port or not.</para>
        /// <para>The Microsoft SerialPort implementation doesn't provide this option
        /// (in fact, in .NET 4.0 it doesn't appear to control this at all).</para>
        /// <para>Some DCE devices will resume sending after any character arrives. 
        /// The TxContinueOnXoff member should be set to FALSE when communicating with
        /// a DCE device that resumes sending after any character arrives.</para>
        /// </remarks>
        public bool TxContinueOnXOff
        {
            get { return m_SerialPort.SerialPortCommState.TxContinueOnXOff; }
            set
            {
                m_SerialPort.SerialPortCommState.TxContinueOnXOff = value;
                if (IsOpen) m_SerialPort.SerialPortCommState.SetCommState();
            }
        }

        /// <summary>
        /// Gets or sets the break signal state.
        /// </summary>
        /// <remarks>
        /// The break signal state occurs when a transmission is suspended and the line is placed
        /// in a break state (all low, no stop bit) until released. To enter a break state, set this
        /// property to true. If the port is already in a break state, setting this property again
        /// to true does not result in an exception. It is not possible to write to the SerialPortStream
        /// while BreakState is true.
        /// </remarks>
        public bool BreakState
        {
            get
            {
                if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
                if (!IsOpen) throw new InvalidOperationException("Serial Port not opened");
                return m_BreakState;
            }
            set
            {
                if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
                if (!IsOpen) throw new InvalidOperationException("Serial Port not opened");
                if (value != m_BreakState) {
                    m_BreakState = value;
                    if (m_BreakState) {
                        m_SerialPort.SerialPortModemStatus.SetCommBreak();
                    } else {
                        m_SerialPort.SerialPortModemStatus.ClearCommBreak();
                    }
                }
            }
        }
        #endregion

        #region Seeking
        /// <summary>
        /// This stream is not seekable, so always returns false.
        /// </summary>
        public override bool CanSeek
        {
            get { return false; }
        }

        /// <summary>
        /// This stream does not support the Length property.
        /// </summary>
        /// <exception cref="NotSupportedException">This stream doesn't support the Length property.</exception>
        public override long Length
        {
            get { throw new NotSupportedException(); }
        }

        /// <summary>
        /// This stream does not support the Position property.
        /// </summary>
        /// <exception cref="NotSupportedException">This stream doesn't support the Position property.</exception>
        public override long Position
        {
            get { throw new NotSupportedException(); }
            set { throw new NotSupportedException(); }
        }

        /// <summary>
        /// This stream does not support seeking.
        /// </summary>
        /// <param name="offset">A byte offset relative to the <paramref name="origin" /> parameter.</param>
        /// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin" /> indicating the reference point used to obtain the new position.</param>
        /// <returns>The new position within the current stream.</returns>
        /// <exception cref="System.NotSupportedException">This stream doesn't support seeking.</exception>
        public override long Seek(long offset, SeekOrigin origin)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// This stream does not support the SetLength property.
        /// </summary>
        /// <param name="value">The desired length of the current stream in bytes.</param>
        /// <exception cref="System.NotSupportedException">This stream doesn't support the SetLength property.</exception>
        public override void SetLength(long value)
        {
            throw new NotSupportedException();
        }
        #endregion

        #region Event Handling and Abstraction
        private object m_EventCheck = new object();

        private ManualResetEvent m_EventProcessing = new ManualResetEvent(false);

        private NativeMethods.SerialEventMask m_CommEvent = 0;

        private NativeMethods.ComStatErrors m_CommErrorEvent = 0;

        private void SerialPortIo_CommEvent(object sender, NativeSerialPort.CommOverlappedIo.CommEventArgs e)
        {
            if (IsDisposed || DataReceived == null && PinChanged == null) {
                m_CommEvent = 0;
                return;
            } else {
                lock (m_EventCheck) {
                    m_CommEvent |= e.EventType;
                }
                CallEvent();
            }
        }

        private void SerialPortIo_CommErrorEvent(object sender, NativeSerialPort.CommOverlappedIo.CommErrorEventArgs e)
        {
            if (IsDisposed || ErrorReceived == null) {
                m_CommErrorEvent = 0;
                return;
            } else {
                lock (m_EventCheck) {
                    m_CommErrorEvent |= e.EventType;
                }
                CallEvent();
            }
        }

        /// <summary>
        /// Determine if we should raise events based on current state
        /// </summary>
        /// <remarks>
        /// This method is designed that it may be called from multiple different
        /// threads. 
        /// </remarks>
        private void CallEvent()
        {
            if (IsDisposed || m_EventProcessing.WaitOne(0)) return;
            m_EventProcessing.Set();

            ThreadPool.QueueUserWorkItem(HandleEvent);
        }

        /// <summary>
        /// Raise events to the calling application of this class
        /// </summary>
        /// <remarks>
        /// This function should be executed from a ThreadPool thread. This method will check
        /// flags, call user events. It will repeat until no events remain pending.
        /// </remarks>
        /// <param name="state">Not used</param>
        private void HandleEvent(object state)
        {
            NativeMethods.SerialEventMask commEvent;
            NativeMethods.ComStatErrors commErrorEvent;

            bool handleEvent;
            lock (m_EventCheck) {
                if (IsDisposed) return;
                handleEvent = (m_CommEvent != 0) || (m_CommErrorEvent != 0);
                commEvent = m_CommEvent;
                m_CommEvent = 0;
                commErrorEvent = m_CommErrorEvent;
                m_CommErrorEvent = 0;
            }

            while (handleEvent) {
                // Received Data
                if ((commEvent & NativeMethods.SerialEventMask.EV_RXFLAG) != 0) {
                    OnDataReceived(this, new SerialDataReceivedEventArgs(SerialData.Eof));
                } else if ((commEvent & NativeMethods.SerialEventMask.EV_RXCHAR) != 0) {
                    bool aboveThreshold;
                    lock (m_EventCheck) {
                        aboveThreshold = m_SerialPort.SerialPortIo.BytesToRead >= m_RxThreshold;
                    }
                    if (aboveThreshold) {
                        OnDataReceived(this, new SerialDataReceivedEventArgs(SerialData.Chars));
                    }
                }

                // Modem Pin States
                if ((commEvent & NativeMethods.SerialEventMask.EV_CTS) != 0) {
                    OnPinChanged(this, new SerialPinChangedEventArgs(SerialPinChange.CtsChanged));
                }
                if ((commEvent & NativeMethods.SerialEventMask.EV_RING) != 0) {
                    OnPinChanged(this, new SerialPinChangedEventArgs(SerialPinChange.Ring));
                }
                if ((commEvent & NativeMethods.SerialEventMask.EV_RLSD) != 0) {
                    OnPinChanged(this, new SerialPinChangedEventArgs(SerialPinChange.CDChanged));
                }
                if ((commEvent & NativeMethods.SerialEventMask.EV_DSR) != 0) {
                    OnPinChanged(this, new SerialPinChangedEventArgs(SerialPinChange.DsrChanged));
                }
                if ((commEvent & NativeMethods.SerialEventMask.EV_BREAK) != 0) {
                    OnPinChanged(this, new SerialPinChangedEventArgs(SerialPinChange.Break));
                }

                // Error States
                if ((commErrorEvent & NativeMethods.ComStatErrors.CE_TXFULL) != 0) {
                    OnCommError(this, new SerialErrorReceivedEventArgs(SerialError.TXFull));
                }
                if ((commErrorEvent & NativeMethods.ComStatErrors.CE_FRAME) != 0) {
                    OnCommError(this, new SerialErrorReceivedEventArgs(SerialError.Frame));
                }
                if ((commErrorEvent & NativeMethods.ComStatErrors.CE_RXPARITY) != 0) {
                    OnCommError(this, new SerialErrorReceivedEventArgs(SerialError.RXParity));
                }
                if ((commErrorEvent & NativeMethods.ComStatErrors.CE_OVERRUN) != 0) {
                    OnCommError(this, new SerialErrorReceivedEventArgs(SerialError.Overrun));
                }
                if ((commErrorEvent & NativeMethods.ComStatErrors.CE_RXOVER) != 0) {
                    OnCommError(this, new SerialErrorReceivedEventArgs(SerialError.RXOver));
                }

                lock (m_EventCheck) {
                    if (IsDisposed) return;
                    handleEvent = (m_CommEvent != 0) || (m_CommErrorEvent != 0);
                    commEvent = m_CommEvent;
                    m_CommEvent = 0;
                    commErrorEvent = m_CommErrorEvent;
                    m_CommErrorEvent = 0;
                }
            }

            lock (m_EventCheck) {
                if (IsDisposed) return;
                m_EventProcessing.Reset();
            };
        }

        /// <summary>
        /// Handles the <see cref="DataReceived" /> event.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="args">The <see cref="SerialDataReceivedEventArgs"/> instance containing the event data.</param>
        protected virtual void OnDataReceived(object sender, SerialDataReceivedEventArgs args)
        {
            SerialDataReceivedEventHandler handler = DataReceived;
            if (handler != null) {
                handler(sender, args);
            }
        }

        /// <summary>
        /// Handles the <see cref="PinChanged" /> event.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="args">The <see cref="SerialPinChangedEventArgs"/> instance containing the event data.</param>
        protected virtual void OnPinChanged(object sender, SerialPinChangedEventArgs args)
        {
            SerialPinChangedEventHandler handler = PinChanged;
            if (handler != null) {
                handler(sender, args);
            }
        }

        /// <summary>
        /// Handles the <see cref="ErrorReceived" /> event.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="args">The <see cref="SerialErrorReceivedEventArgs"/> instance containing the event data.</param>
        protected virtual void OnCommError(object sender, SerialErrorReceivedEventArgs args)
        {
            SerialErrorReceivedEventHandler handler = ErrorReceived;
            if (handler != null) {
                handler(sender, args);
            }
        }
        #endregion

        /// <summary>
        /// Closes the port connection, sets the IsOpen property to false. Does not dispose the object.
        /// </summary>
        /// <remarks>
        /// This method will clean up the object so far as to close the port. Internal buffers remain
        /// active that the stream can continue to read. Writes will throw an exception.
        /// </remarks>
        public new void Close()
        {
            m_SerialPort.Close();
        }

        /// <summary>
        /// Clean up all resources managed by this object.
        /// </summary>
        /// <param name="disposing"><b>true</b> if the user is disposing this object, 
        /// <b>false</b> if being cleaned up by the finaliser.</param>
        protected override void Dispose(bool disposing)
        {
            if (IsDisposed) return;

            if (disposing) {
                // Wait for events to finish before we dispose
                bool eventRunning = false;
                lock (m_EventCheck) {
                    if (m_EventProcessing.WaitOne(0)) {
                        IsDisposed = true;
                        eventRunning = true;
                    }
                }
                if (eventRunning) m_EventProcessing.WaitOne();

                m_Trace.Close();
                if (m_SerialPort != null) {
                    Close();
                    m_SerialPort.Dispose();
                }

                m_EventProcessing.Dispose();
            }
            IsDisposed = true;
            base.Dispose(disposing);
        }

        /// <summary>
        /// Indicates if this object has already been disposed.
        /// </summary>
        public bool IsDisposed { get; private set; }

        /// <summary>
        /// Returns a <see cref="System.String" /> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String" /> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            if (string.IsNullOrWhiteSpace(PortName)) {
                return "SerialPortStream: Invalid configuration";
            }

            char p;
            switch (Parity) {
            case Parity.Even: p = 'E'; break;
            case Parity.Mark: p = 'M'; break;
            case Parity.None: p = 'N'; break;
            case Parity.Odd: p = 'O'; break;
            case Parity.Space: p = 'S'; break;
            default: p = '?'; break;
            }

            string s;
            switch (StopBits) {
            case StopBits.One: s = "1"; break;
            case StopBits.One5: s = "1.5"; break;
            case StopBits.Two: s = "2"; break;
            default: s = "?"; break;
            }

            return string.Format("{0}:{1},{2},{3},{4},to={5},xon={6},odsr={7},octs={8},dtr={9},rts={10},idsr={11}",
                PortName, BaudRate, p, DataBits, s, TxContinueOnXOff ? "on" : "off",
                (Handshake & Handshake.XOn) != 0 ? "on" : "off",
                (Handshake & Handshake.Dtr) != 0 ? "on" : "off",
                (Handshake & Handshake.Rts) != 0 ? "on" : "off",
                (Handshake & Handshake.Dtr) != 0 ? "hs" : (DtrEnable ? "on" : "off"),
                (Handshake & Handshake.Rts) != 0 ? "hs" : (RtsEnable ? "on" : "off"),
                (Handshake & Handshake.Dtr) != 0 ? "on" : "off");
        }
    }
}
